package com.sfeir.client;

import static com.sfeir.client.Case.BACKGROUND_COLOR;
import static com.sfeir.client.util.Constants.SQUARE_SIZE;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.Context2d.LineCap;
import com.google.gwt.canvas.dom.client.Context2d.LineJoin;
import com.google.gwt.canvas.dom.client.CssColor;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.media.client.Audio;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Image;
import com.google.web.bindery.event.shared.EventBus;
import com.sfeir.canvas.activities.client.event.PageChangeEvent;
import com.sfeir.canvas.util.client.ResourceManager;
import com.sfeir.client.i18n.TetrisConstants;
import com.sfeir.client.service.AppServiceAsync;
import com.sfeir.client.tetriselement.TetrisObject;
import com.sfeir.client.tetriselement.TetrisObjectFactory;
import com.sfeir.client.util.Constants;
import com.sfeir.shared.Level;
import com.sfeir.shared.MatrixPoint;

public class GameManager {

	private static final int NB_MAX_ROTATIONS = 3;
	private static final int ROTATION_ANIMATION_REPEAT_INTERVAL = 50;
	private static final int GAME_PANEL_X = 300;
	private static final int GAME_PANEL_Y = 50;

	private static final int GAME_UPDATE = 800;
	private static final int NB_LINES = 20;
	private static final int NB_COLUMNS = 10;
	private static final int SPEED_CHANGE = 80;
	private static final int REPAINT_SQUARE_SIZE = 6;
	private static final Map<Integer, Integer> scores;
	private TetrisConstants constants = GWT.create(TetrisConstants.class);
	
	public enum Sens {
		LEFT, RIGHT
	}

	static {
		scores = new HashMap<Integer, Integer>();
		scores.put(0, 0);
		scores.put(1, 200);
		scores.put(2, 500);
		scores.put(3, 1000);
		scores.put(4, 3000);
	}

	private final Canvas cavas;
	private final Context2d gameCtx;
	private final TetrisObjectFactory factory;

	private boolean isStarted = false;
	private boolean isGameOver = false;
	private boolean isRotating = false;

	private int refreshSpeed;
	// Plateau de jeu, 20 lignes, 10 colonnes
	private Case[][] cases;
	private TetrisObject current;
	private TetrisObject next;
	private Timer gameTimer;
	private Level level;
	private final Audio fall;
	private final Audio harp;
	private final Audio rotate;
	private final Audio down;
	private final AppServiceAsync service;
	private EventBus bus;
	private long startTime;
	private final Image winner;
	private SimpleAnimationTimer rotationTimer;

	public GameManager(ResourceManager manager, AppServiceAsync service) {
		this.cavas = Canvas.createIfSupported();
		this.cavas.setCoordinateSpaceWidth(NB_COLUMNS * Constants.SQUARE_SIZE);
		this.cavas.setCoordinateSpaceHeight(NB_LINES * Constants.SQUARE_SIZE);
		this.cavas.setWidth(NB_COLUMNS * Constants.SQUARE_SIZE + "px");
		this.cavas.setHeight(NB_LINES * Constants.SQUARE_SIZE + "px");
		this.gameCtx = this.cavas.getContext2d();

		this.fall = manager.getAudio(Constants.KEY_AUDIO_FALL);
		this.harp = manager.getAudio(Constants.KEY_AUDIO_HARP);
		this.rotate = manager.getAudio(Constants.KEY_AUDIO_ROTATE);
		this.down = manager.getAudio(Constants.KEY_AUDIO_DOWN);

		this.cases = new Case[NB_LINES][NB_COLUMNS];
		this.factory = new TetrisObjectFactory(manager);
		this.service = service;
		this.winner = manager.getImage(Constants.KEY_IMAGE_WINNER);
	}

	public void setBus(EventBus bus) {
		this.bus = bus;
	}

	private void injectGameContext(Context2d ctx) {
		ctx.drawImage(this.cavas.getCanvasElement(), GAME_PANEL_X, GAME_PANEL_Y);
	}

	public void reloadLevel(Context2d ctx) {
		if (this.level != null) {
			this.loadLevel(this.level, ctx);
		}
	}

	public void reset(Context2d ctx) {
		this.isStarted = false;
		this.isGameOver = false;

		this.current = null;
		this.next = null;

		this.refreshSpeed = GAME_UPDATE;
		for (int y = 0; y < cases.length; y++) {
			for (int x = 0; x < cases[y].length; x++) {
				this.cases[y][x] = new Case();
			}
		}

		this.drawLayout(ctx);

		nextObject(ctx);
	}

	public boolean isGameOver() {
		return this.isGameOver;
	}

	private void playNextMove(Context2d ctx) {
		if (canMoveDown()) { // la pièce bouge vers le bas
			List<Point> origine = current.getPoints();
			current.down(false);
			repaintCurrent(origine, ctx);
		} else { // si elle ne peut pas, on le fige et on teste les nouvelles
					// lignes
			final TetrisObject exCurrent = this.current;
			// ensure rotation state
			if (this.rotationTimer != null) {
				this.rotationTimer.cancel();
			}
			if (isRotating) {
				exCurrent.endRotation();
				repaintCurrent(ctx);
			}
			this.current = null;
			List<Point> points = exCurrent.getPoints();
			for (Point point : points) {
				if (point.y < 0) {
					continue;
				}
				cases[point.y][point.x].setCase(exCurrent);
			}
			final List<Integer> lines = findDoneLines();
			if (lines.isEmpty()) {
				this.fall.play();
				nextObject(ctx);
			} else {
				// score += scores.get(lines.size());
				// nbLines += lines.size();
				// if (nbLines != 0 && nbLines % LINES_CAP == 0) {
				// speedUp();
				// }
				animateremoveLines(lines, ctx);
			}
		}
	}

	private void animateremoveLines(final List<Integer> lines, final Context2d ctx) {
		new SimpleAnimationTimer(14, 50) {
			@Override
			protected void endTimer() {
				for (Integer index : lines) {
					removeLine(index);
				}
				drawLayout(ctx);
				drawCases(ctx);
				nextObject(ctx);
			}

			@Override
			protected void doUpdate() {
				if (this.nbRepetitions == 0) {
					harp.play();
				}
				if (this.nbRepetitions < 10) { // first effect : blink
					CssColor color = getRandomColor();
					for (Integer index : lines) {
						gameCtx.save();
						gameCtx.setFillStyle(color);
						gameCtx.fillRect(0, index * SQUARE_SIZE, cases[index].length * SQUARE_SIZE, SQUARE_SIZE);
						gameCtx.restore();
					}
					injectGameContext(ctx);
				} else { // Second effect : move down
					if (this.nbRepetitions == 12) {
						fall.play();
					}
					for (Integer index : lines) {
						for (int i = 0; i < cases[index].length; i++) {
							Case myCase = cases[index][i];
							myCase.setImage(null);
							myCase.setHeight(myCase.getHeight() / 2);
						}
					}
					drawLayout(ctx);
					drawCases(ctx);
				}
			}
		};
	}

	/**
	 * Method allowing to draw the remaining pieces
	 */
	private void drawRemainingPieces(Context2d ctx) {
		ctx.save();
		ctx.setFillStyle("white");
		ctx.fillRect(GAME_PANEL_X + 12 * SQUARE_SIZE, GAME_PANEL_Y + 5 * SQUARE_SIZE + 15, 5 * SQUARE_SIZE,
				2 * SQUARE_SIZE);
		ctx.setStrokeStyle("gray");
		ctx.setLineWidth(2);
		ctx.rect(GAME_PANEL_X + 12 * SQUARE_SIZE, GAME_PANEL_Y + 5 * SQUARE_SIZE + 15, 5 * SQUARE_SIZE,
				2 * SQUARE_SIZE);
		ctx.setFillStyle("black");
		ctx.setFont("bolder 14px calibri");
		ctx.fillText(constants.remainingPieces(), GAME_PANEL_X + 12 * SQUARE_SIZE + 5, GAME_PANEL_Y + 5
				* SQUARE_SIZE + 35);
		ctx.setFillStyle("gray");
		ctx.setFont("bolder 14px calibri");
		ctx.fillText(this.factory.getRemainingPieces() + "", GAME_PANEL_X + 12 * SQUARE_SIZE + 50, GAME_PANEL_Y
				+ 5 * SQUARE_SIZE + 55);
		ctx.restore();
	}

	private void drawNextObject(Context2d ctx) {
		ctx.save();
		ctx.setFillStyle("white");
		ctx.fillRect(GAME_PANEL_X + 12 * SQUARE_SIZE, GAME_PANEL_Y, 5 * SQUARE_SIZE, 5 * SQUARE_SIZE);
		ctx.setLineCap(LineCap.BUTT);
		ctx.setLineJoin(LineJoin.ROUND);
		ctx.setStrokeStyle("black");
		ctx.rect(GAME_PANEL_X + 12 * SQUARE_SIZE, GAME_PANEL_Y, 5 * SQUARE_SIZE, 5 * SQUARE_SIZE);
		ctx.stroke();
		if (null != next) {
			this.next
					.paintAtLocation(ctx, GAME_PANEL_X + 29 * SQUARE_SIZE / 2, GAME_PANEL_Y + 5 * SQUARE_SIZE / 2);
		}
		ctx.restore();
	}

	/**
	 * Repaints current block and everything around
	 */
	private void repaintCurrent(Context2d ctx) {
		gameCtx.save();
		// paint current block
		Point point = current.getCenter();
		gameCtx.setFillStyle(BACKGROUND_COLOR);
		int width = REPAINT_SQUARE_SIZE;
		int height = REPAINT_SQUARE_SIZE;
		int actualY = point.y - REPAINT_SQUARE_SIZE / 2;
		int actualX = point.x - REPAINT_SQUARE_SIZE / 2;
		if (point.x + REPAINT_SQUARE_SIZE / 2 >= cases[0].length) {
			width = cases[0].length - point.x + REPAINT_SQUARE_SIZE / 2;
		}
		if (actualY < 0) {
			height += actualY;
			actualY = 0;
		}
		if (actualX < 0) {
			width += actualX;
			actualX = 0;

		}
		gameCtx.fillRect(actualX * SQUARE_SIZE, actualY * SQUARE_SIZE, width * SQUARE_SIZE, height * SQUARE_SIZE);
		this.current.paint(gameCtx);
		gameCtx.restore();
		// repaint cases that are not free
		for (int y = point.y - REPAINT_SQUARE_SIZE / 2; y <= point.y + REPAINT_SQUARE_SIZE / 2; y++) {
			if (y < 0 || y >= cases.length) {
				continue;
			}
			for (int x = point.x - REPAINT_SQUARE_SIZE / 2; x <= point.x + REPAINT_SQUARE_SIZE / 2; x++) {
				if (x < 0 || x >= cases[y].length) {
					continue;
				}
				if (!cases[y][x].isFree()) {
					drawCase(x, y);
				}
			}
		}
		this.injectGameContext(ctx);
	}

	private void drawCase(int x, int y) {
		this.gameCtx.save();
		cases[y][x].paint(gameCtx, x, y);
		this.gameCtx.restore();
	}

	private void repaintCurrent(List<Point> points, Context2d ctx) {
		this.gameCtx.save();
		for (Point point : points) {
			if (point.y < 0) {
				continue;
			}
			this.gameCtx.setFillStyle(BACKGROUND_COLOR);
			this.gameCtx.fillRect(point.x * SQUARE_SIZE, point.y * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE);
		}
		this.current.paint(gameCtx);
		this.gameCtx.restore();
		this.injectGameContext(ctx);
	}

	/**
	 * Draw the layout, i.e. clears the game area with the background color
	 */
	private void drawLayout(Context2d ctx) {
		ctx.save();
		// Game panel
		this.gameCtx.setFillStyle(BACKGROUND_COLOR);
		ctx.setStrokeStyle("black");
		this.gameCtx.fillRect(0, 0, NB_COLUMNS * SQUARE_SIZE, NB_LINES * SQUARE_SIZE);
		ctx.strokeRect(GAME_PANEL_X - 1, GAME_PANEL_Y - 1, NB_COLUMNS * SQUARE_SIZE + 2, NB_LINES * SQUARE_SIZE
				+ 2);
		ctx.restore();
	}

	/**
	 * Draw the different cases on the canvas
	 */
	private void drawCases(Context2d ctx) {
		this.gameCtx.save();
		for (int y = 0; y < cases.length; y++) {
			for (int x = 0; x < cases[y].length; x++) {
				Case myCase = this.cases[y][x];
				if (!myCase.isFree()) {
					drawCase(x, y);
				}
			}
		}
		this.injectGameContext(ctx);
		this.gameCtx.restore();
	}

	/**
	 * Method allowing to load a given level
	 * 
	 * @param level
	 */
	public void loadLevel(Level level, Context2d ctx) {
		this.level = level;
		this.factory.initFactorySequence(this.level.getSequence());
		paintMatrixPlateau(this.level.getPlateau(), ctx);
		initNextToBeatArea(level.getOrder(), ctx);
		this.start(ctx);
	}

	/**
	 * Method allowing to initialize the next to beat area
	 */
	private void initNextToBeatArea(int levelOrder, final Context2d ctx) {
		this.service.getNextToBeat(levelOrder, new AsyncCallback<List<Map<String, Object>>>() {

			@Override
			public void onSuccess(List<Map<String, Object>> result) {
				if (result.size() == 1) {
					displayBestScoreArea((Integer) result.get(0).get(Constants.MAP_AUTHENTICATED_USER_BESTSCORE), ctx);
				} else if (result.size() == 2) {
					displayNextToBeatArea((Integer) result.get(0).get(Constants.MAP_AUTHENTICATED_USER_BESTSCORE),
							(String) result.get(1).get(Constants.MAP_NEXT_USER_TOBEAT),
							(Integer) result.get(1).get(Constants.MAP_NEXT_USER_TOBEAT_SCORE), ctx);
				}
			}

			@Override
			public void onFailure(Throwable caught) {
				// TODO log the msgs
				Window.alert("Problem " + caught.toString());
			}
		});
	}

	/**
	 * Method allowing a specified message when the authenticated user have the
	 * best score on the loaded level
	 * 
	 * @param bestScore
	 */
	private void displayBestScoreArea(int bestScore, Context2d ctx) {
		prepareNextToBeatArea(ctx);
		ctx.setFillStyle("black");
		ctx.setFont("bolder 18px calibri");
		ctx.fillText(constants.youHaveTheBestScore() + " " + bestScore, 30, 150);
		ctx.drawImage(ImageElement.as(this.winner.getElement()), 110, 160, 100, 100);
	}

	/**
	 * Method allowing to display the next user to beat by the authenticated
	 * user
	 * 
	 * @param userScore
	 * @param nextToBeatNickName
	 * @param nextToBeatScore
	 */
	private void displayNextToBeatArea(int userScore, String nextToBeatNickName, int nextToBeatScore, Context2d ctx) {
		prepareNextToBeatArea(ctx);
		ctx.setFillStyle("black");
		ctx.setFont("bolder 18px calibri");
		ctx.fillText(constants.nextToBeat(), 60, 150);
		ctx.fillText(constants.nextToBeatScore(), 60, 195);
		ctx.fillText(constants.yourBestScore(), 60, 240);

		ctx.setFillStyle("gray");
		ctx.setFont("bolder 16px calibri");
		ctx.fillText("" + nextToBeatNickName, 75, 165);
		ctx.fillText("" + nextToBeatScore, 105, 210);
		ctx.fillText("" + userScore, 105, 255);

	}

	/**
	 * Method allowing to prepare the next to beat area
	 */
	private void prepareNextToBeatArea(Context2d ctx) {
		ctx.save();
		ctx.setFillStyle("white");
		ctx.setStrokeStyle("gray");
		ctx.setLineWidth(5);
		ctx.fillRect(20, 115, 270, 170);
		ctx.strokeRect(20, 115, 270, 170);
		ctx.restore();
	}

	/**
	 * This method is used to initialize plateau associated to the loaded level
	 * 
	 * @param plateau
	 */
	private void paintMatrixPlateau(List<MatrixPoint> plateau, Context2d ctx) {
		reset(ctx);
		for (MatrixPoint matrixPoint : plateau) {
			cases[matrixPoint.getX()][matrixPoint.getY()].setCase(this.factory.getTerisElementById(matrixPoint
					.getTetrisObjectId()));
		}
		drawCases(ctx);
	}

	/**
	 * start the game
	 */
	public void start(final Context2d ctx) {

		this.isStarted = true;

		this.startTime = System.currentTimeMillis();

		if (this.gameTimer == null) {
			this.gameTimer = new Timer() {
				@Override
				public void run() {
					this.schedule(refreshSpeed);
					if (isRunning() && current != null) {
						playNextMove(ctx);
					}
				}
			};
		}
		this.gameTimer.schedule(GAME_UPDATE);
	}

	/**
	 * Stop the game thread and pause
	 */
	public void stop() {
		isStarted = false;
		this.gameTimer.cancel();
	}

	/**
	 * Checks whether the game is running or is paused
	 * 
	 * @return whether the game is running or is paused
	 */
	private boolean isRunning() {
		return isStarted; // && !isPaused;
	}

	/**
	 * Internal method for rotation handling
	 * 
	 * @param sens
	 *            whether to turn the block in the trigonometric way or not
	 */
	private void rotateBlock(final Sens sens, final Context2d ctx) {
		isRotating = true;
		this.rotate.play();
		this.current.beginRotation(sens);

		this.rotationTimer = new SimpleAnimationTimer(NB_MAX_ROTATIONS, ROTATION_ANIMATION_REPEAT_INTERVAL) {
			@Override
			protected void endTimer() {
				current.endRotation();
				repaintCurrent(ctx);
				isRotating = false;
			}

			@Override
			protected void doUpdate() {
				current.rotate(sens, NB_MAX_ROTATIONS);
				repaintCurrent(ctx);
			};
		};
	}

	/**
	 * Rotates the block in the trigonometric way
	 */
	public void rotateCurrentBlockLeft(Context2d ctx) {
		if (canRotateLeft() && !isRotating) {
			this.rotateBlock(Sens.LEFT, ctx);
		}
	}

	/**
	 * Rotates the block in the anti-trigonometric way
	 */
	public void rotateCurrentBlockRight(Context2d ctx) {
		if (canRotateRight() && !isRotating) {
			this.rotateBlock(Sens.RIGHT, ctx);
		}
	}

	/**
	 * Moves the current block on the left
	 */
	public void moveCurrentBlockLeft(Context2d ctx) {
		if (canMoveLeft()) {
			List<Point> origine = current.getPoints();
			this.current.left();
			repaintCurrent(origine, ctx);
		}
	}

	/**
	 * moves the current block on the right
	 */
	public void moveCurrentBlockRight(Context2d ctx) {
		if (canMoveRight()) {
			List<Point> origine = current.getPoints();
			this.current.right();
			repaintCurrent(origine, ctx);
		}
	}

	/**
	 * Moves the current block down
	 */
	public void moveCurrentBlockDown(Context2d ctx) {
		if (canMoveDown()) {
			List<Point> origine = current.getPoints();
			this.current.down(true);
			repaintCurrent(origine, ctx);
		}
	}

	/**
	 * handles the next object, when the current object is fixed
	 */
	private void nextObject(Context2d ctx) {
		if ((null == next) && this.factory.hasMoreTetrisElement()) {
			next = this.factory.nextObject();
			drawRemainingPieces(ctx);
		}

		if (null == next) {
			final long totalTime = System.currentTimeMillis() - startTime;
			isGameOver = true;
			gameTimer.cancel();
			final int myScore = calculateScore(totalTime);
			if (this.bus != null) {
				// TODO optimisation here
				this.service.saveScore(this.level.getOrder(), myScore, new AsyncCallback<Integer>() {
					@Override
					public void onFailure(Throwable caught) {
						caught.printStackTrace();
					}

					@Override
					public void onSuccess(Integer result) {
						Map<String, Object> context = new HashMap<String, Object>();
						context.put(Constants.LEVEL_PARAM, level);
						context.put("score", myScore);
						context.put("number", result);
						bus.fireEvent(new PageChangeEvent(PageChangeEvent.NEXT, context));

					}
				});
			}

		} else {
			drawRemainingPieces(ctx);
			current = next;
			if (this.factory.hasMoreTetrisElement()) {
				next = this.factory.nextObject();
			} else {
				next = null;
			}
			this.current.centerObject(5, 0);

			drawNextObject(ctx);

			if (!this.arePointsValid(current.getPoints())) {
				isGameOver = true;
			}
		}
	}

	/**
	 * Checks whether the block can move down
	 * 
	 * @return if the block can move down
	 */
	private boolean canMoveDown() {
		if (this.current == null) {
			return false;
		}
		List<Point> points = this.current.getPoints();
		for (Point point : points) {
			if (point.y == cases.length - 1) {
				return false;
			}
			if (point.y < 0) {
				continue;
			}
			Case myCase = cases[point.y + 1][point.x];
			if (!myCase.isFree()) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Checks whether this block can move left
	 * 
	 * @return is the block can move left or not
	 */
	private boolean canMoveLeft() {
		if (this.current == null) {
			return false;
		}
		List<Point> points = this.current.getPoints();
		for (Point point : points) {
			if (point.y < 0) {
				continue;
			}
			if (point.x <= 0) {
				return false;
			}
			Case myCase = cases[point.y][point.x - 1];
			if (!myCase.isFree()) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Checks whether this block can move right
	 * 
	 * @return is the block can move right or not
	 */
	private boolean canMoveRight() {
		if (this.current == null) {
			return false;
		}
		List<Point> points = this.current.getPoints();
		for (Point point : points) {
			if (point.y < 0) {
				continue;
			}
			if (point.x >= cases[0].length - 1) {
				return false;
			}
			Case myCase = cases[point.y][point.x + 1];
			if (!myCase.isFree()) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Checks whether the given list of points are valid, that is to say that
	 * there aren't filled cases at the given location
	 * 
	 * @param points
	 *            the points corresponding to the current brick
	 * @return whether these points are valid or not
	 */
	private boolean arePointsValid(List<Point> points) {
		for (Point point : points) {
			if (point.x >= 0 && point.y >= 0 && point.y < cases.length && point.x < cases[point.y].length) {
				if (!cases[point.y][point.x].isFree()) {
					return false;
				}
			}
			if (point.y >= NB_LINES) {
				return false;
			}
			if (point.y > 0) {
				if (point.x < 0 || point.x >= 10) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Checks if the current block can rotate left
	 * 
	 * @return true if it can, false if it cannot
	 */
	private boolean canRotateLeft() {
		return this.current != null && arePointsValid(this.current.previewRotateLeft());
	}

	/**
	 * Checks if the current block can rotate left
	 * 
	 * @return true if it can, false if it cannot
	 */
	private boolean canRotateRight() {
		return this.current != null && arePointsValid(this.current.previewRotateRight());
	}

	/**
	 * Function returning a random color
	 * 
	 * @return a random color
	 */
	private CssColor getRandomColor() {
		int r = (int) (256 * Math.random());
		int g = (int) (256 * Math.random());
		int b = (int) (256 * Math.random());
		return CssColor.make(r, g, b);
	}

	/**
	 * Find lines that are completed
	 * 
	 * @return the indexes of the lines that are done
	 */
	private List<Integer> findDoneLines() {
		List<Integer> result = new ArrayList<Integer>();
		for (int i = 0; i < cases.length; i++) {
			boolean isComplete = true;
			for (int j = 0; j < cases[i].length; j++) {
				if (cases[i][j].isFree()) {
					isComplete = false;
				}
			}
			if (isComplete) {
				result.add(i);
			}
		}
		return result;
	}

	/**
	 * Removes a given line from the matrix
	 * 
	 * @param index
	 *            the index of the line in the matrix
	 */
	private void removeLine(Integer index) {
		// le but : décaler vers le haut les lignes, c'est � dire, dire faire un
		// - 1 sur les lignes
		for (int i = index; i > 0; i--) {
			for (int j = 0; j < cases[i].length; j++) {
				cases[i][j] = cases[i - 1][j];
			}
		}

		for (int i = 0; i < cases[0].length; i++) {
			cases[0][i] = new Case();
		}
	}

	/**
	 * Changes the speed to be faster
	 */
	public void speedUp() {
		this.refreshSpeed -= SPEED_CHANGE;
		if (this.refreshSpeed < 10) {
			this.refreshSpeed = 10;
		}
	}

	/**
	 * Changes the speed to be slower
	 */
	public void slowDown() {
		this.refreshSpeed += SPEED_CHANGE;
		if (this.refreshSpeed < 10) {
			this.refreshSpeed = 10;
		}
	}

	/**
	 * Drop piece to bottom
	 */
	public void dropPiece(final Context2d ctx) {
		this.down.play();
		new Timer() {
			@Override
			public void run() {
				moveCurrentBlockDown(ctx);
				if (canMoveDown()) {
					this.schedule(30);
				} else {
					playNextMove(ctx);
					this.cancel();
				}
			}
		}.schedule(20);
	}

	private int calculateScore(long time) {
		int nbRemaining = 0;
		for (Case[] tab : cases) {
			for (Case myCase : tab) {
				if (!myCase.isFree()) {
					nbRemaining++;
				}
			}
		}
		return (int) (time / 100) + 100 * nbRemaining;
	}

}
